/**
* @file gui_key_frontend.c
* @brief key frontend
* @details 
* @author huangbin
* @version V1.0.0
* @date 2025-06-12
* @copyright All documents are copyrighted by Shenzhen Xinlongwei Technology Co., LTD.
*            They may not be used for commercial purposes without permission
*/


/*============================ INCLUDES ======================================*/

#define __GUI_MESSAGE_INHERIT__
#define __GUI_KEY_FRONTEND_IMPLEMENT__


#include "./gui_key_frontend.h"

#if defined(__clang__)
//#   pragma clang diagnostic push
#   pragma clang diagnostic ignored "-Wunknown-warning-option"
#   pragma clang diagnostic ignored "-Wreserved-identifier"
#   pragma clang diagnostic ignored "-Wsign-conversion"
#   pragma clang diagnostic ignored "-Wpadded"
#   pragma clang diagnostic ignored "-Wcast-qual"
#   pragma clang diagnostic ignored "-Wcast-align"
#   pragma clang diagnostic ignored "-Wmissing-field-initializers"
#   pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments"
#   pragma clang diagnostic ignored "-Wmissing-prototypes"
#   pragma clang diagnostic ignored "-Wunused-variable"
#   pragma clang diagnostic ignored "-Wunused-parameter"
#   pragma clang diagnostic ignored "-Wgnu-statement-expression"
#   pragma clang diagnostic ignored "-Wtautological-pointer-compare"
#elif __IS_COMPILER_ARM_COMPILER_5__
#elif __IS_COMPILER_GCC__
#   pragma GCC diagnostic push
#   pragma GCC diagnostic ignored "-Wformat="
#   pragma GCC diagnostic ignored "-Wpedantic"
#endif

/*============================ MACROS ========================================*/
#undef this
#define this    (*ptThis)
#undef base
#define base    (*ptBase)
/*============================ MACROFIED FUNCTIONS ===========================*/
/*============================ TYPES =========================================*/

/*============================ PROTOTYPES ====================================*/
static
arm_fsm_rt_t __msg_key_frontend_process(gui_msg_frontend_t *ptBase,
                                        gui_msg_frontend_process_mode_t tMode);

static
gui_msg_core_t *__gui_helper_msg_key_frontend_read_input_fifo(void *pObj);


/*============================ LOCAL VARIABLES ===============================*/
static
const gui_i_msg_frontend_t GUI_HELPER_MSG_KEY_FRONTEND_CHAIN = {
    .fnRead = __gui_helper_msg_key_frontend_read_input_fifo,
};

/*============================ GLOBAL VARIABLES ==============================*/
/*============================ IMPLEMENTATION ================================*/

ARM_NONNULL(1)
void gui_helper_msg_key_frontend_init(  gui_msg_key_frontend_t *ptThis,
                                        gui_msg_key_frontend_cfg_t *ptCFG)
{
    assert(NULL != ptThis);
    assert(NULL != ptCFG);

    memset(ptThis, 0, sizeof(gui_msg_key_frontend_t));

    gui_helper_msg_key_frontend_settings_set(ptThis, ptCFG);

    gui_helper_msg_fifo_init(&this.tInternalFIFO[0]);
    gui_helper_msg_fifo_init(&this.tInternalFIFO[1]);

    arm_with(  gui_msg_frontend_t, 
                &this.use_as__gui_msg_frontend_t) {
        _->fnProcess = &__msg_key_frontend_process;
        _->ptChain = &GUI_HELPER_MSG_KEY_FRONTEND_CHAIN;

        gui_helper_msg_fifo_init(&_->tOutputFIFO);
    }
    
}


ARM_NONNULL(1, 2)
gui_msg_key_frontend_cfg_t *gui_helper_msg_key_frontend_settings_get(
                                        gui_msg_key_frontend_t *ptThis,
                                        gui_msg_key_frontend_cfg_t *ptCFG)
{
    assert(NULL != ptThis);
    assert(NULL != ptCFG);

    *ptCFG = this.tCFG;

    return ptCFG;
}

extern
ARM_NONNULL(1, 2)
void gui_helper_msg_key_frontend_settings_set(
                                        gui_msg_key_frontend_t *ptThis,
                                        gui_msg_key_frontend_cfg_t *ptCFG)
{
    assert(NULL != ptThis);
    assert(NULL != ptCFG);

    this.tCFG = *ptCFG;
    if (0 == this.tCFG.u8LongPressThresholdIn100Ms ) {
        this.tCFG.u8LongPressThresholdIn100Ms = 30;
    }
    if (0 == this.tCFG.u4First3RepeatInterval) {
        this.tCFG.u4First3RepeatInterval = 10;
    }
    if (0 == this.tCFG.u4RestRepeatInterval) {
        this.tCFG.u4RestRepeatInterval = this.tCFG.u4First3RepeatInterval;
    }
    if (0 == this.tCFG.u10DoubleClickLimit) {
        this.tCFG.u10DoubleClickLimit = 333;
    }
}

ARM_NONNULL(1)
bool gui_helper_msg_key_frontend_long_pressing_detection(
                                        gui_msg_key_frontend_t *ptThis,
                                        bool bEnable)
{
    assert(NULL != ptThis);

    bool bOldStatus = !this.tCFG.bDisableLongPressDetection;

    this.tCFG.bDisableLongPressDetection = !bEnable;

    return bOldStatus;
}

ARM_NONNULL(1)
bool gui_helper_msg_key_frontend_double_pressed_detection(
                                        gui_msg_key_frontend_t *ptThis,
                                        bool bEnable)
{
    assert(NULL != ptThis);

    bool bOldStatus = !this.tCFG.bDisableDoublePressedDetection;

    this.tCFG.bDisableDoublePressedDetection = !bEnable;

    return bOldStatus;
}


static
gui_msg_core_t *__gui_helper_msg_key_frontend_read_input_fifo(void *pObj)
{
    assert(NULL != pObj);
    gui_msg_frontend_t *ptThis = (gui_msg_frontend_t *)pObj;

    return gui_helper_msg_fifo_get(&this.tOutputFIFO);
}

__STATIC_INLINE
gui_msg_key_evt_t *__msg_key_frontend_init_a_key_event(
                                                gui_msg_key_evt_t *ptNew,
                                                gui_msg_key_evt_t *ptReference,
                                                uint8_t chID)
{
    ptNew->hwKeyValue = ptReference->hwKeyValue;
    ptNew->use_as__gui_msg_core_t.ptTargetWidget 
        = ptReference->use_as__gui_msg_core_t.ptTargetWidget;
    ptNew->iLastPeriodInMS = 0;
    ptNew->use_as__gui_msg_core_t.u8ID = chID;

    return ptNew;
}


/*----------------------------------------------------------------------------*
 * Key Message Processing Pipeline : Stage 0                                  *
 * -------------------------------------------------------------------------- *
 * Brief: Ensure GUI_MSG_KEY_EVT_DOWN and GUI_MSG_KEY_EVT_UP events are       *
 *        pair-matched.                                                       *
 *----------------------------------------------------------------------------*/
#define STAGE0_RESET_FSM()  do {this.Stage0.chState = START;} while(0)

ARM_NONNULL(1)
static arm_fsm_rt_t __msg_key_frontend_stage0(
                                        gui_msg_key_frontend_t *ptThis)
{
    assert(NULL != ptThis);
    gui_msg_frontend_t *ptBase = 
        &this.use_as__gui_msg_frontend_t;
    assert(NULL != base.Dependency.tIn.fnRead);

    gui_msg_core_t *ptMSG = NULL;
    gui_msg_key_evt_t *ptKeyMSG = NULL;

    enum {
        START,
        WAIT_KEY_DOWN,
        WAIT_KEY_UP,
    };

    switch (this.Stage0.chState) {
        case START:
            this.Stage0.chState++;
            this.Stage0.hwLastKeyValue = GUI_KEY_NONE;
            //fall-through
        
        case WAIT_KEY_DOWN:
            ptMSG = base.Dependency.tIn.fnRead(base.Dependency.tIn.pTarget);
            if (NULL == ptMSG) {
                return arm_fsm_rt_cpl;
            }

            /* filter for key message */
            if (__GUI_MSG_KEY_EVT != (ptMSG->u8ID & __GUI_MSG_KEY_EVT_MSK)) {

                /* send to the next stage directly */
                gui_helper_msg_fifo_append(&this.tInternalFIFO[0], ptMSG);
                break;
            }

            ptKeyMSG = (gui_msg_key_evt_t *)ptMSG;

            if (ptMSG->u8ID == GUI_MSG_KEY_EVT_UP) {                            /* abnormal condition */
                /* this shouldn't happen, we suppose this is a mistake, let's 
                 * correct it 
                 */
                gui_msg_key_evt_t *ptMSGDown = 
                    (gui_msg_key_evt_t *)gui_helper_msg_pool_new(base.ptPool);

                if (NULL == ptMSGDown) {
                    /* looks like we don't have sufficient resource */
                    if (this.tCFG.bRemoveUnexpectedKeyMessage) {
                        gui_helper_msg_pool_free(base.ptPool, (gui_msg_t *)ptMSG);
                    } else {
                        /* send this message to the next stage */
                        gui_helper_msg_fifo_append(&this.tInternalFIFO[0], ptMSG);
                    }
                    break;
                }

                /* init an KEY_DOWN message */
                __msg_key_frontend_init_a_key_event(ptMSGDown, 
                                                    ptKeyMSG, 
                                                    GUI_MSG_KEY_EVT_DOWN);

                /* send this message to the next stage */
                gui_helper_msg_fifo_append( &this.tInternalFIFO[0], 
                                            (gui_msg_core_t *)ptMSGDown);
                gui_helper_msg_fifo_append( &this.tInternalFIFO[0], 
                                            (gui_msg_core_t *)ptKeyMSG);

                STAGE0_RESET_FSM();
                return arm_fsm_rt_cpl;
            } else if (ptMSG->u8ID != GUI_MSG_KEY_EVT_DOWN) {
                /* for other type of key event */

                if (this.tCFG.bRemoveUnexpectedKeyMessage) {
                    gui_helper_msg_pool_free(base.ptPool, (gui_msg_t *)ptMSG);
                } else {
                    /* send this message to the next stage */
                    gui_helper_msg_fifo_append(&this.tInternalFIFO[0], ptMSG);
                }
                break;
            }

            /* this normal condition: key down */

            /* record the key value */
            this.Stage0.hwLastKeyValue = ptKeyMSG->hwKeyValue;
            this.Stage0.ptTargetWidget = ptMSG->ptTargetWidget;

            ptKeyMSG->iLastPeriodInMS = 0;

            /* send this message to the next stage */
            gui_helper_msg_fifo_append(&this.tInternalFIFO[0], ptMSG);

            this.Stage0.chState = WAIT_KEY_UP;
            //fall-through

        case WAIT_KEY_UP:

            ptMSG = base.Dependency.tIn.fnRead(base.Dependency.tIn.pTarget);
            if (NULL == ptMSG) {
                return arm_fsm_rt_wait_for_obj;
            }

            /* filter for key message */
            if (__GUI_MSG_KEY_EVT != (ptMSG->u8ID & __GUI_MSG_KEY_EVT_MSK)) {

                /* send to the next stage directly */
                gui_helper_msg_fifo_append(&this.tInternalFIFO[0], ptMSG);
                break;
            }

            ptKeyMSG = (gui_msg_key_evt_t *)ptMSG;

            if (ptMSG->u8ID == GUI_MSG_KEY_EVT_DOWN) {
                /* duplicated key down */
                if (ptKeyMSG->hwKeyValue == this.Stage0.hwLastKeyValue) {
                    /* delete it directly */
                    gui_helper_msg_pool_free(base.ptPool, (gui_msg_t *)ptMSG);
                } else {
                    /* a different one */
                    gui_msg_key_evt_t *ptMSGUp = (gui_msg_key_evt_t *)
                        gui_helper_msg_pool_new(base.ptPool);

                    if (NULL == ptMSGUp) {
                        /* looks like we don't have sufficient resource */
                        if (this.tCFG.bRemoveUnexpectedKeyMessage) {
                            gui_helper_msg_pool_free(base.ptPool, (gui_msg_t *)ptMSG);
                        } else {
                            /* send this message to the next stage */
                            gui_helper_msg_fifo_append(&this.tInternalFIFO[0], ptMSG);
                        }
                        break;
                    }

                    /* init an KEY_UP message */
                    ptMSGUp->use_as__gui_msg_core_t.ptTargetWidget = this.Stage0.ptTargetWidget;
                    ptMSGUp->use_as__gui_msg_core_t.u8ID = GUI_MSG_KEY_EVT_UP;
                    ptMSGUp->hwKeyValue = this.Stage0.hwLastKeyValue;
                    ptMSGUp->iLastPeriodInMS = 0;

                    /* send this message to the next stage */
                    gui_helper_msg_fifo_append( &this.tInternalFIFO[0], 
                                                (gui_msg_core_t *)ptMSGUp);

                    
                    /* update last key message */
                    this.Stage0.hwLastKeyValue = ptKeyMSG->hwKeyValue;
                    this.Stage0.ptTargetWidget = ptMSG->ptTargetWidget;

                    ptKeyMSG->iLastPeriodInMS = 0;

                    /* send this message to the next stage */
                    gui_helper_msg_fifo_append( &this.tInternalFIFO[0], 
                                                (gui_msg_core_t *)ptKeyMSG);
                    break;
                }
            } else if (ptMSG->u8ID == GUI_MSG_KEY_EVT_UP) {
                /* normal condition */

                if (ptKeyMSG->hwKeyValue == this.Stage0.hwLastKeyValue) {
                    /* expected condition */

                    /* send this message to the next stage */
                    gui_helper_msg_fifo_append( &this.tInternalFIFO[0], 
                                                (gui_msg_core_t *)ptKeyMSG);
                    
                    STAGE0_RESET_FSM();
                    return arm_fsm_rt_cpl;
                } else {
                    /* a different key */

                    gui_msg_key_evt_t *ptOldMSGUp = (gui_msg_key_evt_t *)
                        gui_helper_msg_pool_new(base.ptPool);
                    
                    gui_msg_key_evt_t *ptNewMSGDown = (gui_msg_key_evt_t *)
                        gui_helper_msg_pool_new(base.ptPool);
                    
                    if (NULL == ptOldMSGUp || NULL == ptNewMSGDown) {
                        /* looks like we don't have sufficient resource */
                        gui_helper_msg_pool_free(base.ptPool, (gui_msg_t *)ptOldMSGUp);
                        gui_helper_msg_pool_free(base.ptPool, (gui_msg_t *)ptNewMSGDown);

                        if (this.tCFG.bRemoveUnexpectedKeyMessage) {
                            gui_helper_msg_pool_free(base.ptPool, (gui_msg_t *)ptMSG);
                        } else {
                            /* send this message to the next stage */
                            gui_helper_msg_fifo_append(&this.tInternalFIFO[0], ptMSG);
                        }
                        break;
                    }

                    /* init an KEY_UP message for the old key */
                    ptOldMSGUp->use_as__gui_msg_core_t.ptTargetWidget = this.Stage0.ptTargetWidget;
                    ptOldMSGUp->use_as__gui_msg_core_t.u8ID = GUI_MSG_KEY_EVT_UP;
                    ptOldMSGUp->hwKeyValue = this.Stage0.hwLastKeyValue;

                    /* init an KEY_DOWN message for the new key */
                    __msg_key_frontend_init_a_key_event(ptNewMSGDown, 
                                                        ptKeyMSG, 
                                                        GUI_MSG_KEY_EVT_DOWN);


                    /* send this message to the next stage */
                    gui_helper_msg_fifo_append( &this.tInternalFIFO[0], 
                                                (gui_msg_core_t *)ptOldMSGUp);
                    gui_helper_msg_fifo_append( &this.tInternalFIFO[0], 
                                                (gui_msg_core_t *)ptNewMSGDown);
                    gui_helper_msg_fifo_append( &this.tInternalFIFO[0], 
                                                (gui_msg_core_t *)ptKeyMSG);

                    STAGE0_RESET_FSM();
                    return arm_fsm_rt_cpl;
                }

            } else {
                if (this.tCFG.bRemoveUnexpectedKeyMessage) {
                    gui_helper_msg_pool_free(base.ptPool, (gui_msg_t *)ptMSG);
                } else {
                    /* send this message to the next stage */
                    gui_helper_msg_fifo_append(&this.tInternalFIFO[1], ptMSG);
                }
            }
            break;
    }

    return arm_fsm_rt_on_going;
}


/*----------------------------------------------------------------------------*
 * Key Message Processing Pipeline : Stage 1                                  *
 * -------------------------------------------------------------------------- *
 * Brief: Detect and append the following event:                              *
 *      GUI_MSG_KEY_EVT_PRESSED                                               *
 *      GUI_MSG_KEY_EVT_LONG_PRESSING                                         *
 *      GUI_MSG_KEY_EVT_REPEAT                                                *
 *----------------------------------------------------------------------------*/

static 
arm_fsm_rt_t 
__msg_key_frontend_stage1_wait_key_up_only( gui_msg_key_frontend_t *ptThis, 
                                            int16_t iElapsedMs)
{
    assert(NULL != ptThis);
    gui_msg_frontend_t *ptBase = 
        &this.use_as__gui_msg_frontend_t;
    assert(NULL != base.Dependency.tIn.fnRead);

    gui_msg_core_t *ptMSG = NULL;
    gui_msg_key_evt_t *ptKeyMSG = NULL;

    do {
        /* peek from the internal fifo 0 */
        ptMSG = gui_helper_msg_fifo_peek(&this.tInternalFIFO[0]);

        if (NULL == ptMSG) {
            return arm_fsm_rt_wait_for_obj;
        }

        /* filter for key message */
        if (__GUI_MSG_KEY_EVT != (ptMSG->u8ID & __GUI_MSG_KEY_EVT_MSK)) {

            /* drop the key up event*/
            gui_helper_msg_fifo_get(&this.tInternalFIFO[0]);

            /* send to the next stage directly */
            gui_helper_msg_fifo_append(&this.tInternalFIFO[1], ptMSG);
            break;
        }

        ptKeyMSG = (gui_msg_key_evt_t *)ptMSG;

        if (ptMSG->u8ID == GUI_MSG_KEY_EVT_UP) {
            if (ptKeyMSG->hwKeyValue != this.Stage1.hwLastKeyValue) {

                /* drop the key up event*/
                gui_helper_msg_fifo_get(&this.tInternalFIFO[0]);

                /* unexpected condition: since we have stage0, we choose
                    * to use a simple strategy to handle this
                    */
                if (this.tCFG.bRemoveUnexpectedKeyMessage) {
                    gui_helper_msg_pool_free(base.ptPool, (gui_msg_t *)ptMSG);
                } else {
                    /* send this message to the next stage */
                    gui_helper_msg_fifo_append(&this.tInternalFIFO[1], ptMSG);
                }
                break;
            }

            ptKeyMSG->iLastPeriodInMS = iElapsedMs;

            /* drop the key up event*/
            gui_helper_msg_fifo_get(&this.tInternalFIFO[0]);

            /* send the key up message to the next stage */
            gui_helper_msg_fifo_append(&this.tInternalFIFO[1], ptMSG);

            /* finishing detect a key pressed event */
            return arm_fsm_rt_cpl;

        } else if (ptMSG->u8ID == GUI_MSG_KEY_EVT_DOWN) {

            /* drop the key up event*/
            gui_helper_msg_fifo_get(&this.tInternalFIFO[0]);

            /* unexpected condition: since we have stage0, we choose
                * to use a simple strategy to handle this
                */
            if (this.tCFG.bRemoveUnexpectedKeyMessage) {
                gui_helper_msg_pool_free(base.ptPool, (gui_msg_t *)ptMSG);
            } else {
                /* send this message to the next stage */
                gui_helper_msg_fifo_append(&this.tInternalFIFO[1], ptMSG);
            }
            break;
        } else {
            /* drop the key up event*/
            gui_helper_msg_fifo_get(&this.tInternalFIFO[0]);

            /* other key events
             * send this message to the next stage 
             */
            gui_helper_msg_fifo_append(&this.tInternalFIFO[1], ptMSG);
        }
    } while(0);

    return arm_fsm_rt_on_going;
}

#define STAGE1_RESET_FSM()  do {this.Stage1.chState = START;} while(0)

ARM_NONNULL(1)
static arm_fsm_rt_t __msg_key_frontend_stage1(
                                        gui_msg_key_frontend_t *ptThis)
{
    assert(NULL != ptThis);
    gui_msg_frontend_t *ptBase = 
        &this.use_as__gui_msg_frontend_t;
    assert(NULL != base.Dependency.tIn.fnRead);

    gui_msg_core_t *ptMSG = NULL;
    gui_msg_key_evt_t *ptKeyMSG = NULL;
    int64_t lElapsedMs = 0;
    arm_fsm_rt_t tResult;

    enum {
        START,
        WAIT_KEY_DOWN,
        CHECK_REPEAT_FIRST3,
        CHECK_REPEAT_REST,
        WAIT_KEY_UP,
    };

    switch (this.Stage1.chState) {
        case START:
            this.Stage1.chState++;
            this.Stage1.hwLastKeyValue = GUI_KEY_NONE;
            //fall-through

        case WAIT_KEY_DOWN:
            /* read from the internal fifo 0 */
            ptMSG = gui_helper_msg_fifo_get(&this.tInternalFIFO[0]);
            if (NULL == ptMSG) {
                return arm_fsm_rt_cpl;
            }

            /* filter for key message */
            if (__GUI_MSG_KEY_EVT != (ptMSG->u8ID & __GUI_MSG_KEY_EVT_MSK)) {

                /* send to the next stage directly */
                gui_helper_msg_fifo_append(&this.tInternalFIFO[1], ptMSG);
                break;
            }

            ptKeyMSG = (gui_msg_key_evt_t *)ptMSG;

            if (ptMSG->u8ID == GUI_MSG_KEY_EVT_UP) {
                /* unexpected condition: since we have stage0, we choose
                 * to use a simple strategy to handle this
                 */
                if (this.tCFG.bRemoveUnexpectedKeyMessage) {
                    gui_helper_msg_pool_free(base.ptPool, (gui_msg_t *)ptMSG);
                } else {
                    /* send this message to the next stage */
                    gui_helper_msg_fifo_append(&this.tInternalFIFO[1], ptMSG);
                }
                break;
            } else if (ptMSG->u8ID != GUI_MSG_KEY_EVT_DOWN) {
                /* other key event
                 * send this message to the next stage 
                 */
                gui_helper_msg_fifo_append(&this.tInternalFIFO[1], ptMSG);
                break;
            } 
            /* normal condition: key down */

            this.Stage1.hwLastKeyValue = ptKeyMSG->hwKeyValue;
            this.Stage1.ptTargetWidget = ptKeyMSG->use_as__gui_msg_core_t.ptTargetWidget;
            ptKeyMSG->iLastPeriodInMS = 0;

            /* set the starting point */
            this.lTimestamp[0] = arm_2d_helper_get_system_timestamp();

            /* pass the key down message to the next stage */
            gui_helper_msg_fifo_append(&this.tInternalFIFO[1], ptMSG);
            
            this.Stage1.chState = WAIT_KEY_UP;

            //fall-through
        
        case WAIT_KEY_UP:
            /* check timeout first */
            do {
                int64_t lCurrent = arm_2d_helper_get_system_timestamp();

                lElapsedMs = arm_2d_helper_convert_ticks_to_ms(lCurrent - this.lTimestamp[0]);
                if (this.tCFG.bDisableLongPressDetection) {

                    if (lElapsedMs >= (this.tCFG.u4First3RepeatInterval * 100ul)) {

                        this.lTimestamp[1] = this.lTimestamp[0];
                        if (this.tCFG.u4First3RepeatInterval == this.tCFG.u4RestRepeatInterval) {
                            this.Stage1.chState = CHECK_REPEAT_REST;
                        } else {
                            this.Stage1.chState = CHECK_REPEAT_FIRST3;
                        }
                        return arm_fsm_rt_on_going;
                    }
                } else if (lElapsedMs >= (this.tCFG.u8LongPressThresholdIn100Ms * 100ul)) {  /* check long-pressing threshold */
                    
                    /* detect a long press */
                    gui_msg_key_evt_t *ptMSGLongPressing = 
                        (gui_msg_key_evt_t *)gui_helper_msg_pool_new(base.ptPool);
                    
                    if (NULL == ptMSGLongPressing) {
                        /* we don't have sufficient resource, let's try next time */
                        continue;
                    }

                    ptMSGLongPressing->iLastPeriodInMS = MIN(lElapsedMs, __INT16_MAX__);
                    ptMSGLongPressing->use_as__gui_msg_core_t.ptTargetWidget = this.Stage1.ptTargetWidget;
                    ptMSGLongPressing->use_as__gui_msg_core_t.u8ID = GUI_MSG_KEY_EVT_LONG_PRESSING;
                    ptMSGLongPressing->hwKeyValue = this.Stage1.hwLastKeyValue;

                    gui_helper_msg_fifo_append( &this.tInternalFIFO[1], 
                                                (gui_msg_core_t *)ptMSGLongPressing);

                    /* reset timer */
                    this.lTimestamp[1] = lCurrent;

                    if (this.tCFG.u4First3RepeatInterval == this.tCFG.u4RestRepeatInterval) {
                        this.Stage1.chState = CHECK_REPEAT_REST;
                    } else {
                        this.Stage1.chState = CHECK_REPEAT_FIRST3;
                    }
                    return arm_fsm_rt_on_going;
                }
            } while(0);

            /* peek from the internal fifo 0 */
            ptMSG = gui_helper_msg_fifo_peek(&this.tInternalFIFO[0]);

            if (NULL == ptMSG) {
                return arm_fsm_rt_wait_for_obj;
            }

            /* filter for key message */
            if (__GUI_MSG_KEY_EVT != (ptMSG->u8ID & __GUI_MSG_KEY_EVT_MSK)) {

                /* drop the key up event*/
                gui_helper_msg_fifo_get(&this.tInternalFIFO[0]);

                /* send to the next stage directly */
                gui_helper_msg_fifo_append(&this.tInternalFIFO[1], ptMSG);
                break;
            }

            ptKeyMSG = (gui_msg_key_evt_t *)ptMSG;

            if (ptMSG->u8ID == GUI_MSG_KEY_EVT_UP) {
                if (ptKeyMSG->hwKeyValue != this.Stage1.hwLastKeyValue) {

                    /* drop the key up event*/
                    gui_helper_msg_fifo_get(&this.tInternalFIFO[0]);

                    /* unexpected condition: since we have stage0, we choose
                     * to use a simple strategy to handle this
                     */
                    if (this.tCFG.bRemoveUnexpectedKeyMessage) {
                        gui_helper_msg_pool_free(base.ptPool, (gui_msg_t *)ptMSG);
                    } else {
                        /* send this message to the next stage */
                        gui_helper_msg_fifo_append(&this.tInternalFIFO[1], ptMSG);
                    }
                    break;
                }

                /* detect a key pressed event */
                gui_msg_key_evt_t *ptMSGPressed = 
                    (gui_msg_key_evt_t *)gui_helper_msg_pool_new(base.ptPool);
                
                if ((NULL == ptMSGPressed) && (lElapsedMs < 300)) {
                    /* we don't have sufficient resource, let's try next time */
                    break;
                }

                if (NULL != ptMSGPressed) {
                    /* initialize this key pressed event */
                    __msg_key_frontend_init_a_key_event(ptMSGPressed, 
                                                        ptKeyMSG, 
                                                        GUI_MSG_KEY_EVT_PRESSED);

                    ptMSGPressed->iLastPeriodInMS = MIN(lElapsedMs, __INT16_MAX__);
                    ptKeyMSG->iLastPeriodInMS = MIN(lElapsedMs, __INT16_MAX__);

                    /* send the key pressed message to the next stage */
                    gui_helper_msg_fifo_append(&this.tInternalFIFO[1], (gui_msg_core_t *)ptMSGPressed);
                }

                /* drop the key up event*/
                gui_helper_msg_fifo_get(&this.tInternalFIFO[0]);

                /* send the key up message to the next stage */
                gui_helper_msg_fifo_append(&this.tInternalFIFO[1], ptMSG);
                /* finishing detect a key pressed event */
                STAGE1_RESET_FSM();
                return arm_fsm_rt_cpl;

            } else if (ptMSG->u8ID == GUI_MSG_KEY_EVT_DOWN) {

                /* drop the key up event*/
                gui_helper_msg_fifo_get(&this.tInternalFIFO[0]);

                /* unexpected condition: since we have stage0, we choose
                 * to use a simple strategy to handle this
                 */
                if (this.tCFG.bRemoveUnexpectedKeyMessage) {
                    gui_helper_msg_pool_free(base.ptPool, (gui_msg_t *)ptMSG);
                } else {
                    /* send this message to the next stage */
                    gui_helper_msg_fifo_append(&this.tInternalFIFO[1], ptMSG);
                }
                break;
            } else {
                /* drop the key up event*/
                gui_helper_msg_fifo_get(&this.tInternalFIFO[0]);

                /* other key events
                 * send this message to the next stage 
                 */
                gui_helper_msg_fifo_append(&this.tInternalFIFO[1], ptMSG);
            }
            break;
        
        case CHECK_REPEAT_FIRST3:
            /* check timeout */
            do {
                int64_t lCurrent = arm_2d_helper_get_system_timestamp();
                int64_t lElapsedMsShort = arm_2d_helper_convert_ticks_to_ms(lCurrent - this.lTimestamp[1]);
                lElapsedMs = arm_2d_helper_convert_ticks_to_ms(lCurrent - this.lTimestamp[0]);
                if (lElapsedMsShort >= (this.tCFG.u4First3RepeatInterval * 100ul)) {  /* check long-pressing threshold */
                    
                    /* detect a long press */
                    gui_msg_key_evt_t *ptMSGRepeat = 
                        (gui_msg_key_evt_t *)gui_helper_msg_pool_new(base.ptPool);
                    
                    if (NULL == ptMSGRepeat) {
                        /* we don't have sufficient resource, let's try next time */
                        break;
                    }

                    ptMSGRepeat->iLastPeriodInMS = MIN(lElapsedMs, __INT16_MAX__);
                    ptMSGRepeat->use_as__gui_msg_core_t.ptTargetWidget = this.Stage1.ptTargetWidget;
                    ptMSGRepeat->use_as__gui_msg_core_t.u8ID = GUI_MSG_KEY_EVT_REPEAT;
                    ptMSGRepeat->hwKeyValue = this.Stage1.hwLastKeyValue;

                    gui_helper_msg_fifo_append( &this.tInternalFIFO[1], 
                                                (gui_msg_core_t *)ptMSGRepeat);

                    /* reset timer */
                    this.lTimestamp[1] = lCurrent;

                    if (this.tCFG.bDisableLongPressDetection) {
                        if (lElapsedMs >= (this.tCFG.u4First3RepeatInterval * 300ul)) {
                            this.Stage1.chState = CHECK_REPEAT_REST;
                        }
                    } else {
                        if (lElapsedMs 
                        >= (    (this.tCFG.u8LongPressThresholdIn100Ms * 100ul)
                        +    this.tCFG.u4First3RepeatInterval * 300ul)) {
                            this.Stage1.chState = CHECK_REPEAT_REST;
                        }
                    }
                }
            } while(0);
            tResult = __msg_key_frontend_stage1_wait_key_up_only(
                                                ptThis, 
                                                MIN(lElapsedMs, __INT16_MAX__));
            if (arm_fsm_rt_cpl == tResult) {
                STAGE1_RESET_FSM();
                return arm_fsm_rt_cpl;
            } else if (arm_fsm_rt_wait_for_obj == tResult) {
                return arm_fsm_rt_wait_for_obj;
            }
            break;
        
        case CHECK_REPEAT_REST:
            /* check timeout */
            do {
                int64_t lCurrent = arm_2d_helper_get_system_timestamp();
                int64_t lElapsedMsShort = arm_2d_helper_convert_ticks_to_ms(lCurrent - this.lTimestamp[1]);
                lElapsedMs = arm_2d_helper_convert_ticks_to_ms(lCurrent - this.lTimestamp[0]);
                if (lElapsedMsShort >= (this.tCFG.u4RestRepeatInterval * 100ul)) {  /* check long-pressing threshold */
                    
                    /* detect a long press */
                    gui_msg_key_evt_t *ptMSGRepeat = 
                        (gui_msg_key_evt_t *)gui_helper_msg_pool_new(base.ptPool);
                    
                    if (NULL == ptMSGRepeat) {
                        /* we don't have sufficient resource, let's try next time */
                        break;
                    }

                    ptMSGRepeat->iLastPeriodInMS = MIN(lElapsedMs, __INT16_MAX__);
                    ptMSGRepeat->use_as__gui_msg_core_t.ptTargetWidget = this.Stage1.ptTargetWidget;
                    ptMSGRepeat->use_as__gui_msg_core_t.u8ID = GUI_MSG_KEY_EVT_REPEAT;
                    ptMSGRepeat->hwKeyValue = this.Stage1.hwLastKeyValue;

                    gui_helper_msg_fifo_append( &this.tInternalFIFO[1], 
                                                (gui_msg_core_t *)ptMSGRepeat);

                    /* reset timer */
                    this.lTimestamp[1] = lCurrent;
                }
            } while(0);

            tResult = __msg_key_frontend_stage1_wait_key_up_only(
                                                ptThis, 
                                                MIN(lElapsedMs, __INT16_MAX__));
            if (arm_fsm_rt_cpl == tResult) {
                STAGE1_RESET_FSM();
                return arm_fsm_rt_cpl;
            } else if (arm_fsm_rt_wait_for_obj == tResult) {
                return arm_fsm_rt_wait_for_obj;
            }
            break;
    }

    return arm_fsm_rt_on_going;
}

/*----------------------------------------------------------------------------*
 * Key Message Processing Pipeline : Stage 2                                  *
 * -------------------------------------------------------------------------- *
 * Brief: Detect and append GUI_MSG_KEY_EVT_DOUBLE_PRESSED                    *
 *----------------------------------------------------------------------------*/

#define STAGE2_RESET_FSM()  do {this.Stage2.chState = START;} while(0)

ARM_NONNULL(1)
static arm_fsm_rt_t __msg_key_frontend_stage2(
                                        gui_msg_key_frontend_t *ptThis)
{
    assert(NULL != ptThis);
    gui_msg_frontend_t *ptBase = 
        &this.use_as__gui_msg_frontend_t;
    assert(NULL != base.Dependency.tIn.fnRead);

    gui_msg_core_t *ptMSG = NULL;
    int64_t lElapsedMs = 0;

ARM_PT_BEGIN(this.Stage2.chPT)

    gui_helper_msg_fifo_init(&this.Stage2.tTempFIFO);

        /* wait first key pressed event */
        do {
    ARM_PT_ENTRY()
            /* read from the internal fifo 1 */
            ptMSG = gui_helper_msg_fifo_get(&this.tInternalFIFO[1]);
            if (NULL == ptMSG) {
                ARM_PT_GOTO_PREV_ENTRY(arm_fsm_rt_cpl);
            }

            /* filter for key message */
            if (    (ptMSG->u8ID != GUI_MSG_KEY_EVT_PRESSED)
                ||  this.tCFG.bDisableDoublePressedDetection) {

                /* send to the output stage */
                gui_helper_msg_fifo_append(&base.tOutputFIFO, ptMSG);
                continue;
            }


            gui_helper_msg_fifo_append(&this.Stage2.tTempFIFO, ptMSG);
        
            /* start counting */
            this.lTimestamp[2] = arm_2d_helper_get_system_timestamp();
            break;
            
        } while(true);

/*- Small Task Loop ------------------------------------------------- */
    ARM_PT_ENTRY()
//label_wait_2nd_key_pressed_event_start:
        /* wait for the 2nd key pressed event */
        do {
            int64_t lElapsedMs = arm_2d_helper_convert_ticks_to_ms(
                                        arm_2d_helper_get_system_timestamp() 
                                    -   this.lTimestamp[2]);

            if (lElapsedMs >= this.tCFG.u10DoubleClickLimit) {
                /* timeout */

                /* send the first key pressed message to the output fifo */
                ptMSG = gui_helper_msg_fifo_get(&this.Stage2.tTempFIFO);
                gui_helper_msg_fifo_append(&base.tOutputFIFO, ptMSG);

                /* complete with the normal exit point */
                //goto label_stage2_exit;
                break;
            }

            ptMSG = gui_helper_msg_fifo_peek(&this.tInternalFIFO[1]);
            if (NULL == ptMSG) {
                ARM_PT_GOTO_PREV_ENTRY(arm_fsm_rt_wait_for_obj);
/*------------------------------------------------------------------- */
            }
            gui_msg_key_evt_t *ptKeyMSG = (gui_msg_key_evt_t *)ptMSG;

            /* filter for key message */
            if (ptMSG->u8ID != GUI_MSG_KEY_EVT_PRESSED) {
                
                /* drop the event */
                gui_helper_msg_fifo_get(&this.tInternalFIFO[1]);

                /* send to the output stage */
                gui_helper_msg_fifo_append(&base.tOutputFIFO, ptMSG);
                //goto label_wait_2nd_key_pressed_event_start;
                continue;
            }

            gui_msg_key_evt_t *ptLastMSG = (gui_msg_key_evt_t *)
                gui_helper_msg_fifo_peek(&this.Stage2.tTempFIFO);

            assert(NULL != ptLastMSG);
            assert(NULL != ptMSG);

            if (    (ptKeyMSG->hwKeyValue == ptLastMSG->hwKeyValue)
               &&   (ptMSG->ptTargetWidget == ptLastMSG->use_as__gui_msg_core_t.ptTargetWidget)) {
        
                
                /* drop the event */
                gui_msg_key_evt_t *ptPressedMSG = 
                    (gui_msg_key_evt_t *)gui_helper_msg_fifo_get(&this.tInternalFIFO[1]);
                assert(NULL != ptPressedMSG);
                gui_helper_msg_pool_free(base.ptPool, (gui_msg_t *)ptPressedMSG);


                /* reuse the first key pressed message */
                gui_msg_key_evt_t *ptMSGDoublePressed = 
                    (gui_msg_key_evt_t *)gui_helper_msg_fifo_get(&this.Stage2.tTempFIFO);
                assert(NULL != ptMSGDoublePressed);
                
                /* initialize this key pressed event */
                ptMSGDoublePressed->use_as__gui_msg_core_t.u8ID = GUI_MSG_KEY_EVT_DOUBLE_PRESSED;
                    ptMSGDoublePressed->iLastPeriodInMS = MIN(lElapsedMs, __INT16_MAX__);

                    /* send the double pressed key event */

                    gui_helper_msg_fifo_append( &base.tOutputFIFO, 
                                                (gui_msg_core_t *)ptMSGDoublePressed);


                /* complete with the normal exit point */
                //goto label_stage2_exit;
                break;
            } else {
                /* detect a different key pressed event */

                /* send the first key pressed message to the fifo */
                ptMSG = gui_helper_msg_fifo_get(&this.Stage2.tTempFIFO);
                assert(NULL != ptMSG);

                gui_helper_msg_fifo_append(&base.tOutputFIFO, ptMSG);

                /* reset the PT task */
                ARM_PT_RETURN(arm_fsm_rt_on_going);
            }

        } while(true);

/* the normal exit point */
//label_stage2_exit:

ARM_PT_END();

    return arm_fsm_rt_cpl;
}

static
arm_fsm_rt_t __msg_key_frontend_process(gui_msg_frontend_t *ptBase,
                                        gui_msg_frontend_process_mode_t tMode)
{
    gui_msg_key_frontend_t *ptThis = (gui_msg_key_frontend_t *)ptBase;

    assert(NULL != base.Dependency.tIn.fnRead);
    arm_fsm_rt_t tResult = arm_fsm_rt_cpl;

    /* run until we reach the complete condition */
    do {
        tResult = arm_fsm_rt_cpl;

        /* stage 0*/
        if (tMode == GUI_FRONTEND_PROCESS_MODE_ALL || tMode == GUI_FRONTEND_PROCESS_MODE_CRITICAL) {
            arm_fsm_rt_t tReturn = __msg_key_frontend_stage0(ptThis);

            switch (tReturn) {
                case arm_fsm_rt_cpl:
                    break;
                case arm_fsm_rt_wait_for_obj:
                    if (tResult == arm_fsm_rt_cpl) {
                        tResult = arm_fsm_rt_wait_for_obj;
                    }
                    break;
                
                default:
                    tResult = tReturn;
                    break;
            }
        }

        /* stage 1*/
        if (tMode == GUI_FRONTEND_PROCESS_MODE_ALL || tMode == GUI_FRONTEND_PROCESS_MODE_NON_CRITICAL) {
            do {
                arm_fsm_rt_t tReturn = __msg_key_frontend_stage1(ptThis);

                switch (tReturn) {
                    case arm_fsm_rt_cpl:
                        break;
                    case arm_fsm_rt_wait_for_obj:
                        if (tResult == arm_fsm_rt_cpl) {
                            tResult = arm_fsm_rt_wait_for_obj;
                        }
                        break;
                    
                    default:
                        tResult = tReturn;
                        break;
                }
            } while(0);
            
            /* stage 2*/
            do {
                arm_fsm_rt_t tReturn = __msg_key_frontend_stage2(ptThis);

                switch (tReturn) {
                    case arm_fsm_rt_cpl:
                        break;
                    case arm_fsm_rt_wait_for_obj:
                        if (tResult == arm_fsm_rt_cpl) {
                            tResult = arm_fsm_rt_wait_for_obj;
                        }
                        break;
                    
                    default:
                        tResult = tReturn;
                        break;
                }
            } while(0);
        }

        if (tResult < 0) {
            /* error detected */
            break;
        } else if ( (tResult == arm_fsm_rt_cpl) 
                 || (tResult == arm_fsm_rt_wait_for_obj)) {
            /* complete condition */
            break;
        }
    } while(gui_helper_msg_fifo_is_empty(&this.use_as__gui_msg_frontend_t.tOutputFIFO));

    return tResult;
}

